package com.niodata.dp.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.log4j.Logger;
import org.yaml.snakeyaml.Yaml;

/**
 * properties load configuration.
 */
public class PropertiesConfiguration {

  private static final Logger logger = Logger.getLogger(PropertiesConfiguration.class);
  private Properties properties;

  private PropertiesConfiguration() {
  }

  /**
   * load config from files.
   * @param files source property、yml files
   * @return config
   * @throws IOException ex
   */
  public static PropertiesConfiguration loadConfig(File... files) throws IOException {
    PropertiesConfiguration config = new PropertiesConfiguration();
    config.properties = new Properties();
    for (File file : files) {
      if (!file.exists()) {
        continue;
      }
      InputStream stream = new FileInputStream(file);
      if (file.getName().endsWith("properties")) {
        PropertiesConfiguration tmp = PropertiesConfiguration.loadProperties(stream);
        config.merge(tmp);
      } else if (file.getName().endsWith("yml") || file.getName().endsWith("yaml")) {
        PropertiesConfiguration tmp = PropertiesConfiguration.loadYaml(stream);
        config.merge(tmp);
      }
      stream.close();
    }
    return config;
  }

  public static PropertiesConfiguration loadProperties(InputStream inputStream) throws IOException {
    PropertiesConfiguration config = new PropertiesConfiguration();
    Properties properties = new Properties();
    properties.load(inputStream);
    inputStream.close();
    config.properties = properties;
    return config;
  }

  /**
   * load config from classpath properties files.
   * @param fileNames classpath property file names
   * @return config
   * @throws IOException ex
   */
  public static PropertiesConfiguration loadClassPathConfig(String... fileNames)
        throws IOException {
    PropertiesConfiguration config = new PropertiesConfiguration();
    Properties properties = new Properties();
    config.properties = properties;
    for (String file : fileNames) {
      URL url = PropertiesConfiguration.class.getResource(file);
      if (url == null) {
        continue;
      }
      String path = url.getPath();
      InputStream stream = PropertiesConfiguration.class.getResourceAsStream(file);
      if (path.endsWith("properties")) {
        PropertiesConfiguration tmp = PropertiesConfiguration.loadProperties(stream);
        config.merge(tmp);
      } else if (path.endsWith("yml") || path.endsWith("yaml")) {
        PropertiesConfiguration tmp = PropertiesConfiguration.loadYaml(stream);
        config.merge(tmp);
      }
      stream.close();
    }
    return config;
  }

  public void merge(PropertiesConfiguration other) {
    this.properties.putAll(other.properties);
  }

  public String getString(String key) {
    if (!properties.containsKey(key)) {
      throw new RuntimeException(String.format("property key not found: %s", key));
    }
    String value = properties.getProperty(key);
    return value;
  }

  public String getString(String key, String defaultValue) {
    String value = properties.getProperty(key);
    if (value == null) {
      logger.warn(String
            .format("Can't find config for key '%s', use default value: %s", key, defaultValue));
      value = defaultValue;
    }
    return value;
  }


  /**
   * get config value sep by , as list.
   * @param key conf key
   * @return list
   */
  public List<String> getStringList(String key) {
    if (!properties.containsKey(key)) {
      throw new RuntimeException(String.format("property key not found: %s", key));
    }
    String value = properties.getProperty(key);
    return Arrays.asList(value.split(","));
  }

  public List<String> getStringList(String key, String sep) {
    String value = getString(key);
    return Arrays.asList(value.split(sep));
  }


  public int getInt(String key) {
    return Integer.parseInt(properties.getProperty(key));
  }

  public int getInt(String key, int defaultValue) {
    if (properties.containsKey(key)) {
      return Integer.parseInt(properties.getProperty(key));
    }
    return defaultValue;
  }

  public boolean getBoolean(String key) {
    return Boolean.parseBoolean(properties.getProperty(key));
  }

  public boolean getBoolean(String key, boolean defaultValue) {
    if (properties.containsKey(key)) {
      return Boolean.parseBoolean(properties.getProperty(key));
    }
    return defaultValue;
  }

  public long getLong(String key) {
    return Long.parseLong(properties.getProperty(key));
  }

  public long getLong(String key, long defaultValue) {
    if (properties.containsKey(key)) {
      return Long.parseLong(properties.getProperty(key));
    }
    return defaultValue;
  }

  public Map<String, String> getConfByPrefix(String prefix) {
    return getConfByPrefix(prefix, true);
  }

  /**
   * get all config by prefix.
   * @param prefix prefix
   * @param trimPrefix is prefix trimed
   * @return values
   */
  public Map<String, String> getConfByPrefix(String prefix, boolean trimPrefix) {
    Set<Object> keys = properties.keySet();
    Map<String, String> map = new HashMap<>();
    for (Object key : keys) {
      String ks = key.toString();
      String[] segs = ks.split("\\.");
      if (segs[0].equals(prefix) && segs.length > 1) {
        if (trimPrefix) {
          map.put(ks.replace(prefix + ".", ""), properties.getProperty(ks));
        } else {
          map.put(ks, properties.getProperty(ks));
        }
      }
    }
    return map;
  }

  public static PropertiesConfiguration loadYaml(File yamlFile) throws IOException {
    InputStream input = new FileInputStream(yamlFile);
    return loadYaml(input);
  }

  public static PropertiesConfiguration loadYaml(InputStream inputStream) throws IOException {
    Yaml yaml = new Yaml();
    Map<Object, Object> obj = yaml.load(inputStream);
    Map<String, String> map = new HashMap<>();
    parseYamlToMap(map, obj, null);
    Properties properties = new Properties();
    properties.putAll(map);
    PropertiesConfiguration config = new PropertiesConfiguration();
    config.properties = properties;
    inputStream.close();
    return config;
  }

  private static void parseYamlToMap(Map<String,String> map, Map<Object, Object> yaml, String key) {
    for (Map.Entry<Object, Object> entry:yaml.entrySet()) {
      Object k = entry.getKey();
      Object v = entry.getValue();
      if (! (v instanceof Map)) {
        if (key == null) {
          map.put(k.toString(), v.toString());
        } else {
          map.put(key + "." + k.toString(), v.toString());
        }
      } else {
        if (key == null) {
          parseYamlToMap(map,(Map<Object, Object>)v,k.toString());
        } else {
          parseYamlToMap(map,(Map<Object, Object>)v,key + "." + k.toString());
        }
      }
    }
  }
}
